/* 
 * Copyright (C) 2004-2005 Jonathan Bindel
 * Copyright (C) 2006-2007 Eskil Bylund
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

using System;
using System.Collections.Generic;
using System.Timers;

using DCSharp.Backend.Connections;
using DCSharp.Backend.Objects;

namespace DCSharp.Backend.Managers
{
	public class ResultReceivedEventArgs : EventArgs
	{
		private SearchInfo searchInfo;
		private SearchResult result;

		public ResultReceivedEventArgs(SearchInfo searchInfo,
			SearchResult result)
		{
			this.searchInfo = searchInfo;
			this.result = result;
		}

		public SearchInfo SearchInfo
		{
			get { return searchInfo; }
		}

		public SearchResult Result
		{
			get { return result; }
		}
	}

	/// <summary>
	/// Passes search requests to the connected hubs and handles the incoming
	/// search results.
	/// </summary>
	public class SearchManager
	{
		public EventHandler<SearchEventArgs> SearchStarted;
		public EventHandler<SearchEventArgs> SearchStopped;
		public EventHandler<ResultReceivedEventArgs> ResultReceived;

		private HubManager hubManager;

		private List<SearchInfo> searches;
		private Dictionary<SearchInfo, Timer> timers;

		public SearchManager(HubManager hubManager)
		{
			if (hubManager == null)
			{
				throw new ArgumentNullException("hubManager");
			}
			this.hubManager = hubManager;

			searches = new List<SearchInfo>();
			timers = new Dictionary<SearchInfo, Timer>();
		}

		#region Methods

		public void Search(SearchInfo searchInfo)
		{
			Search(searchInfo, TimeSpan.Zero);
		}

		public void Search(SearchInfo searchInfo, TimeSpan timeout)
		{
			Search(searchInfo, timeout, hubManager.Hubs);
		}

		public void Search(SearchInfo searchInfo, TimeSpan timeout,
			params HubConnection[] hubs)
		{
			if (searchInfo == null)
			{
				throw new ArgumentNullException("searchInfo");
			}
			lock (searches)
			{
				if (searches.Contains(searchInfo))
				{
					return;
				}
				searches.Add(searchInfo);
				StartTimer(searchInfo, timeout);
			}
			TrySearch(hubs, searchInfo);
			OnSearchStarted(searchInfo);
		}

		public void StopSearch(SearchInfo searchInfo)
		{
			if (searchInfo == null)
			{
				throw new ArgumentNullException("searchInfo");
			}
			lock (searches)
			{
				if (!searches.Contains(searchInfo))
				{
					return;
				}
				searches.Remove(searchInfo);
				StopTimer(searchInfo);
			}
			OnSearchStopped(searchInfo);
		}

		public bool Contains(SearchInfo searchInfo)
		{
			return searches.Contains(searchInfo);
		}

		public void HandleIncomingResult(SearchResult result)
		{
			if (result == null)
			{
				throw new ArgumentNullException("result");
			}
			SearchInfo[] toMatch;
			lock (searches)
			{
				toMatch = searches.ToArray();
			}
			foreach (SearchInfo searchInfo in toMatch)
			{
				if (Match(searchInfo, result))
				{
					OnResultReceived(searchInfo, result);
				}
			}
		}

		protected static bool Match(SearchInfo searchInfo, SearchResult result)
		{
			// TODO: Check the type.
			if (searchInfo.TTH == result.TTH)
			{
				return true;
			}
			if ((searchInfo.MaxSize != null && result.Size > searchInfo.MaxSize) ||
				(searchInfo.MinSize != null && result.Size < searchInfo.MinSize))
			{
				return false;
			}
			if (searchInfo.Keywords != null &&
				!StringUtil.MatchesKeywords(result.Path, searchInfo.Keywords))
			{
				return false;
			}
			return true;
		}

		protected virtual void OnSearchStarted(SearchInfo searchInfo)
		{
			if (SearchStarted != null)
			{
				SearchStarted(this, new SearchEventArgs(searchInfo));
			}
		}

		protected virtual void OnSearchStopped(SearchInfo searchInfo)
		{
			if (SearchStopped != null)
			{
				SearchStopped(this, new SearchEventArgs(searchInfo));
			}
		}

		protected virtual void OnResultReceived(SearchInfo searchInfo,
			SearchResult result)
		{
			if (ResultReceived != null)
			{
				ResultReceived(this, new ResultReceivedEventArgs(searchInfo,
					result));
			}
		}

		private void StartTimer(SearchInfo searchInfo, TimeSpan timeout)
		{
			if (timeout != TimeSpan.Zero)
			{
				Timer timer = new Timer(timeout.TotalMilliseconds);
				timer.Elapsed += delegate
				{
					StopSearch(searchInfo);
				};
				timers.Add(searchInfo, timer);
				timer.Start();
			}
		}

		private void StopTimer(SearchInfo searchInfo)
		{
			Timer timer;
			if (timers.TryGetValue(searchInfo, out timer))
			{
				timer.Dispose();
				timers.Remove(searchInfo);
			}
		}

		private void TrySearch(HubConnection[] hubs, SearchInfo searchInfo)
		{
			foreach (HubConnection hub in hubs)
			{
				try
				{
					hub.Search(searchInfo);
				}
				catch
				{
				}
			}
		}

		#endregion
	}
}
